/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands.call;

import android.media.MediaPlayer;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.objects.XREObject;

import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Helper class for this component.
 * <p>
 * <b>Thread safety:</b> This class is immutable and so thread safe.
 *
 * @author orial
 * @version 1.0
 */
@SuppressWarnings("boxing")
public final class Helper {
    /** Represents call command methods, that requires the XREView to be repainted. */
    static final Set<String> XREVIEW_MOD_METHODS =
        new HashSet<String>(Arrays.asList("addChild", "removeChild", "setChildIndex", "activate"));

    /**
     * This private constructor prevents to create a new instance.
     */
    private Helper() {
        // empty
    }

    /**
     * Checks whether the given {@link Object} is null.
     *
     * @param arg the argument to check
     * @param name the name of the argument to check
     * @throws IllegalArgumentException if the given object is null
     */
    public static void checkNotNull(Object arg, String name) {
        if (arg == null) {
            throw new IllegalArgumentException("Argument '" + name + "' value should not be null.");
        }
    }

    /**
     * Checks whether the given {@link String} is null or empty (the length of the given string is zero after it is
     * trimmed).
     *
     * @param arg the String to check
     * @param name the name of the String argument to check
     * @throws IllegalArgumentException if the given string is null or empty
     */
    public static void checkString(String arg, String name) {
        checkNotNull(arg, name);
        if (arg.trim().length() == 0) {
            throw new IllegalArgumentException("Argument '" + name + "' value should not be empty string.");
        }
    }

    /**
     * Checks byte array.
     *
     * @param byteArray the array to check
     * @param name the name of byte array
     * @throws IllegalArgumentException if array is null or empty
     */
    public static void checkByteArray(byte[] byteArray, String name) {
        checkNotNull(byteArray, name);
        if (byteArray.length == 0) {
            throw new IllegalArgumentException("Argument '" + name + "' value should not be empty array.");
        }
    }

    /**
     * Closes stream.
     *
     * @param stream the stream to close
     */
    public static void closeStream(Closeable stream) {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore
            }
        }
    }

    /**
     * Checks whether the given {@link Object} is null.
     *
     * @param arg the argument to check
     * @param name the name of the argument to check
     * @throws IllegalStateException if the given object is null
     */
    static void checkState(Object arg, String name) {
        if (arg == null) {
            throw new IllegalStateException("Value of '" + name + "' should not be null.");
        }
    }

    /**
     * Checks {@link XRECallCommand}.
     *
     * @param command the command to check
     * @return casted {@link XRECallCommand}
     * @throws IllegalArgumentException if command is null or not of {@link XRECallCommand} type
     */
    static XRECallCommand checkCommand(XRECommand command) {
        checkNotNull(command, "command");

        // check command type
        if (command instanceof XRECallCommand) {
            return (XRECallCommand) command;
        }
        throw new IllegalArgumentException("Argument 'command' is not instance of XRECallCommand.");
    }

    /**
     * Checks if receiver is correct and gets target object.
     *
     * @param <T> type of target object
     * @param receiver the current receiver to check
     * @param callCommand the current {@link XRECallCommand}
     * @param expectedClass expected class of target object
     * @return target object
     * @throws IllegalArgumentException if receiver is not correct, or target object is not correct
     */
    @SuppressWarnings("unchecked")
    static <T extends XREObject> T checkReceiver(AndroidReceiver receiver, XRECallCommand callCommand,
        Class<T> expectedClass) {
        checkNotNull(receiver, "receiver");
        if (receiver.getApplicationData() == null) {
            throw new IllegalArgumentException("Value of 'receiver.applicationData' should not be null.");
        }
        if (receiver.getApplicationData().getElementsXREIdToObject() == null) {
            throw new IllegalArgumentException("Value of "
                + "'receiver.applicationData.elementsXREIdToObject' should not be null.");
        }

        // check target object
        XREObject xreObject = receiver.getApplicationData().getElementsXREIdToObject().get(callCommand.getTargetId());
        if (expectedClass.isInstance(xreObject)) {
            return (T) xreObject;
        }
        throw new IllegalArgumentException("Target object with ID '" + callCommand.getTargetId()
            + "' must be instance of '" + expectedClass.getName() + "' class.");
    }

    /**
     * Logs method enter.
     *
     * @param logger the logger to use
     * @param klass the object, where logging is done
     * @param methodName the name of method, where logging is done
     * @param params the method parameters
     */
    static void logEnter(Logger logger, Object klass, String methodName, Object... params) {
        if (logger != null) {
            logger.entering(klass.getClass().getName(), methodName, params);
        }
    }

    /**
     * Logs method exit.
     *
     * @param logger the logger to use
     * @param klass the object, where logging is done
     * @param methodName the name of method, where logging is done
     */
    static void logExit(Logger logger, Object klass, String methodName) {
        if (logger != null) {
            logger.exiting(klass.getClass().getName(), methodName);
        }
    }

    /**
     * Logs exception and throws it.
     *
     * @param <T> type of occurred exception
     * @param logger the logger to use
     * @param klass the object, where logging is done
     * @param methodName the name of method, where logging is done
     * @param e the exception, that occurred in method
     * @throws T thrown exception
     */
    static <T extends Exception> void logError(Logger logger, Object klass, String methodName, T e) throws T {
        if (logger != null) {
            logger.logp(Level.SEVERE, klass.getClass().getName(), methodName, "Exception occurred, details: "
                + e.getMessage() + ".", e);
        }
        throw e;
    }

    /**
     * Gets {@link MediaPlayer} for given media object.
     *
     * @param receiver the android receiver
     * @param playersName the name of media players group
     * @param id the id of the target object
     * @return corresponding {@link MediaPlayer}
     * @throws IllegalArgumentException if receiver is not correct
     * @throws IllegalStateException if player is not found
     */
    static MediaPlayer getMediaPlayer(AndroidReceiver receiver, String playersName, int id) {
        // temporary data
        Map<String, Object> data = receiver.getApplicationData().getExtraPainterData();
        String name = "receiver.applicationData.extraPainterData";

        // check data
        checkNotNull(data, name);

        // get corresponding players
        Map<?, ?> players = getObject(data.get(playersName), name + "[" + playersName + "]", Map.class);

        // get player
        Object player = players.get(id);
        if (player == null) {
            throw new IllegalStateException("Player in '" + playersName + "' for id '" + id + "' was not found.");
        }

        // check type of player
        return getObject(player, name + "[" + playersName + "][" + id + "]", MediaPlayer.class);
    }

    /**
     * Checks if object is not null and as correct type.
     *
     * @param <T> the expected object's type
     * @param object the object to check
     * @param name the name of the object
     * @param expectedClass the expected object's class
     * @return the casted object to required class
     * @throws IllegalArgumentException if object is null or has incorrect type
     */
    @SuppressWarnings("unchecked")
    static <T> T getObject(Object object, String name, Class<T> expectedClass) {
        checkNotNull(object, name);
        if (expectedClass.isInstance(object)) {
            return (T) object;
        }
        throw new IllegalArgumentException("Object '" + name + "' is not instance of " + expectedClass.getName()
            + " class.");
    }

    /**
     * Gets command parameter.
     *
     * @param <T> expected type of parameter
     * @param callCommand the call command
     * @param index the parameter index
     * @param expectedClass expected type of parameter
     * @return the command parameter
     * @throws IllegalArgumentException if command parameter is not correct
     */
    static <T> T getParam(XRECallCommand callCommand, int index, Class<T> expectedClass) {
        // check params
        checkNotNull(callCommand.getParams(), "command.params");

        // check if index is not out of range
        if (callCommand.getParams().size() <= index) {
            throw new IllegalArgumentException("Command expected to have at least " + (index + 1) + " parameter(s).");
        }

        // get parameter value and cast it to expected class
        Object paramValue = callCommand.getParams().get(index);
        if (expectedClass.isInstance(paramValue)) {
            return expectedClass.cast(paramValue);
        }
        throw new IllegalArgumentException("Command parameter #" + index + " should be instance of '"
            + expectedClass.getName() + "'.");
    }
}
